#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>


#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

#include "Socket-Server.h"
#include "serializable.h"
#include "raid.h"
#include "lista.h"
#include "listaHD.h"
#include "sincronizacion.h"

/*
//CONSTANTES
static int cREAD=0;
static int cWRITE=1;

//GLOBALES
pthread_mutex_t sem_prueba;
tLista Pedidos;
Planificadores Planif;
*/


/*
 * Encola pedido de lectura en la cola de pedidos Gral del FS.
 */
//int leer_Sector(int  s){
int SocketReciveFS_read(int sock, int32_t s){
	//Armo el pedido
	Pedido* p=(Pedido*) malloc(sizeof(Pedido));;
		p->length=0;
		p->Info=NULL;
		p->Sector=s;
		p->Tipo=cREAD;
		p->statusPD=0;
        p->Owner=0;
        p->sock=sock;
    lista_push(&Pedidos, p);
	//signal(s_pedidosFS)

	return 0;
}

/*
 * encola pedidos de escritura en la cola de pedidos Gral del FS.
 */
//int escribir_Sector(int s, buffer* b){
int SocketReciveFS_write(int sock, int32_t s, void* b, int len){
	//Armo el pedido
	Pedido* p=(Pedido*) malloc(sizeof(Pedido));;
		p->length=len;
		p->Info=b;
		p->Sector=s;
		p->Tipo=cWRITE;
		p->statusPD=0;
		p->Owner=0;
		p->sock=sock;
	lista_push(&Pedidos, p);
	//signal(s_pedidosFS)

	return 0;
}

/*
 * elije un disco para la lectura y envia el pedido
 */
int EnviarLecturaPlanif(Planificadores* ps, Pedido* p ){
	//Se elije el disco que pueda antender el sector solicitado
	//es decir no este sincronizando esa zona y ademas el disco que
	//tenga menor pedidos en su lista.

	pthread_mutex_lock(&s_pop_HD[0]);

	int encontre=0;
	if (ps->length>0){
		Disco* hd=NULL;
		Disco* hdmin=NULL;

		tNodoHD* n;
		HDlista_getfirst(ps, &n);
		while(n!=NULL){
			hd=&n->Dato;
			if (
				(
						((hd->Sincornizando)&&(p->Sector <= hd->Sector_Hasta_Sincronizado))
						|| !(hd->Sincornizando)
				)
				&& !(hd->Desconectado)
			){
				encontre=1;
				if (hdmin==NULL){
					hdmin=hd;
				}else{
					if (hdmin->Pedidos->length > hd->Pedidos->length){
						hdmin=hd;
					}
				}
			}
			HDlista_getNext(ps, &n);
		}

		if (encontre){
			hd=hdmin;
			printf ("pedido de LECTURA FS hd, sector: %i %i\n",hd->id, p->Sector);

			lista_push((hd->Pedidos), p);
			//signal(s_pedidoshd[])
		}else{
			printf ("NO HAY DISCO PARA ATENDER PEDIDO\n");
		}

	}else{
		printf ("NO HAY DISCO PARA ATENDER PEDIDO\n");
	}
	pthread_mutex_unlock(&s_pop_HD[0]);
	return encontre;
}

/*
 * manda a escribir a todos los disco un determinado pedido
 */
int EnviarEscituraPlanif(Planificadores* ps, Pedido* p ){
	//envio a escribir a cada uno de los disco.
	pthread_mutex_lock(&s_pop_HD[1]);
	int envio=0;
	if (ps->length>0){
		printf ("pedido de WRITE FS TODOS LOS DISCOS, sector: %i\n", p->Sector);

		tNodoHD* n;
		HDlista_getfirst(ps, &n);
		while(n!=NULL){
			printf ("pedido de WRITE FS hd, sector: %i %i\n",n->Dato.id, p->Sector);

			//hago una copiar
			Pedido* ped=(Pedido*) malloc(sizeof(Pedido));
				ped->length=0;
				ped->Info=NULL;
				ped->Sector=p->Sector;
				ped->Tipo=p->Tipo;
				ped->statusPD=p->statusPD;
				ped->Owner=p->Owner;
				ped->sock=p->sock;

			if (p->Info!=NULL){
				ped->Info = malloc(p->length);
				memcpy(ped->Info, p->Info, p->length);
				ped->length=p->length;
			}
			lista_push((n->Dato.Pedidos), ped);

			HDlista_getNext(ps, &n);
			envio=1;
		}
		//free(p->Info);
	}else{
		printf ("NO HAY DISCO PARA ATENDER PEDIDO\n");
	}
	pthread_mutex_unlock(&s_pop_HD[1]);
	return envio;
}


//1.hilo de Mirada de los pedidos del FS, y envia a cada cola de disco.
void* ProcesarPedidosFS(void* vplanif){
	Planificadores* planif= (Planificadores*) vplanif;

	Pedido* ped;
	while(1){
	//while (Pedidos.length > 0){
		if(planif->length>0){
			if (Pedidos.length > 0){

				tNodo* n;
				n=lista_getfirst(&Pedidos);
				//tengo que copiar el pedido
				ped=lista_popNodo(&Pedidos, n);

				//wait(s_pedidosFS)
				//lista_pop(&Pedidos, &ped);	//	QuitarCola();
				int res=0;
				if (ped->Tipo == cREAD){
					res=EnviarLecturaPlanif(planif, ped);
				}else{
					res=EnviarEscituraPlanif(planif, ped);
					//por que adentro realiza copia
					free(ped->Info);
					free(ped);
				}
				//if (res){
					//tDato d=lista_popNodo(&Pedidos, n);
					//free(d.Info);
				//}else{
				//	printf("NO HAY DISCO MAESTRO, PARA ANTENDER PEDIDOS...");
				//	return ProcesarPedidosFS;
				//}
			}
		}
	}
	return ProcesarPedidosFS;
}


int VerifAllHD(Planificadores* ps, Pedido* p){
	//reccorro todos los planificadores.
	//si alguno tiene el pedido en estatus invalido
	return 1;
}


//busco el disco que se desconecto y paso los pedidos al la Cola del FS.
void SocketHD_Disconect(Planificadores* ps, int sock){
	pthread_mutex_lock(&s_pop_HD[0]);
	pthread_mutex_lock(&s_pop_HD[1]);
	pthread_mutex_lock(&s_pop_HD[2]);
	pthread_mutex_lock(&s_pop_HD[3]);

	//busco el disco
	tNodoHD* n=NULL;
	Disco* hd=NULL;
	if (ps->length>0){
		HDlista_getfirst(ps, &n);
		while(n!=NULL){
			if(n->Dato.Socket==sock){
				hd=&n->Dato;
				break;
			}
			HDlista_getNext(ps, &n);
		}
	}
	if (hd==NULL) {
		printf("ERROR no se encontro disco en lista de hds");
		return;
	}

	hd->Desconectado=1;
	//pthread_mutex_lock(&hdb->s_Desconexion);

	//busco todos los pedidos de este disco
	//para cada pedido lo mando como si fuera del FS, para que la carga quede distribuida.

	if (hd->Pedidos->length >0){
		tNodo* nP;
		nP=lista_getfirst(hd->Pedidos);
		Pedido* ped;
		while(nP!=NULL){
			//wait(s_pedidosHD[])
			//pongo el pedido en algun otro disco.

			ped=lista_popNodo(hd->Pedidos, nP);
			if (ped->Owner==0){//solo los pedidos q viene del FS
				printf (" replanifico, caida de disco Pedido hd, secto, estador: %i %i %i\n",hd->id, ped->Sector, ped->statusPD);
				if (ped->Tipo == cREAD){
					ped->statusPD=0;
					lista_push(&Pedidos, ped);
				}else{
					//solo paso los de lectura
					free(ped->Info);
					free(ped);
				}
			}else{
				free(ped->Info);
				free(ped);
			}
			nP=lista_getNext(hd->Pedidos, nP);

		}
	}
	free(hd->Pedidos);
	//pthread_mutex_unlock(&hdb->s_Desconexion);
	HDlista_popNodo(ps, n);

	pthread_mutex_unlock(&s_pop_HD[0]);
	pthread_mutex_unlock(&s_pop_HD[1]);
	pthread_mutex_unlock(&s_pop_HD[2]);
	pthread_mutex_unlock(&s_pop_HD[3]);
}

void* hiloRecive(void* param){
	int Descriptor_Cliente=*(int*)param;

	/* Bucle hasta que hayamos leído todos los caracteres que
	estamos esperando */
	char* buffer=(char*) malloc(50);
	int Aux;
	int rtaOK=0;
	int32_t t_disk=0;
	//while (1)
	{
		Message aHandshakeMsg;
		Serializable_t aSerializable;

		memset(buffer, 0, 50);
		Aux=recv(Descriptor_Cliente, buffer, 50, MSG_CONFIRM);

		if (Aux > 0)
		{
			/* Se incrementa el número de caracteres leídos */
			printf("\n hay DATOS");
			//deserealizo el sms
			aSerializable.Data = buffer;
			Deserialize_Message(&aHandshakeMsg, aSerializable);

			if ((aHandshakeMsg.type == 0 ) && (aHandshakeMsg.contentSize != 0)){
				printf("tamano disco: \n %i", aHandshakeMsg.sectorRequest);
				t_disk=aHandshakeMsg.sectorRequest;
				aHandshakeMsg.type= msgHandShake;
				aHandshakeMsg.sectorRequest = 0;
				aHandshakeMsg.contentSize= 0;
				aHandshakeMsg.content= "";
				Serialize_Message(&aSerializable,aHandshakeMsg);

				printf("-%s\n",aSerializable.Data );
				int32_t enviados=send(Descriptor_Cliente, aSerializable.Data,aSerializable.Size,MSG_CONFIRM);
				printf("PPD-RAIDenviados: %i\n", enviados);
				Serializable_Free(&aSerializable);

				rtaOK=1;
			}
		}
		else
		{
			if (Aux == 0)
			{
				printf ("Socket cerrado\n");
			}
		}
	}
	if (rtaOK){ //handshake ok
		Disco d1;
			d1.Sector_Fin=t_disk;//1000000
			//d1.Sector_Fin=10000;//1000000
			d1.Sector_Ini=0;
			d1.Sincornizando=0;
			d1.Sector_Hasta_Parcial=-1;
			d1.Sector_Hasta_Sincronizado=0;
			d1.Desconectado=0;
			//d1.hilo=NULL;
			d1.id=Descriptor_Cliente;
			d1.Socket=Descriptor_Cliente;
		d1.Pedidos = (tLista*) malloc(sizeof(tLista));
		lista_init(d1.Pedidos);
		ConectarDisco(&Planif, d1);

		free(buffer);

		char *paqueteReciv;
		Message aMsgRecv;
		Serializable_t aSerializable;
		int32_t reciv;

		//paqueteReciv = malloc(600);
		while(1){
			paqueteReciv = (char*) malloc(600);
			do{
				reciv = 0;
				reciv = recv(Descriptor_Cliente, paqueteReciv, HeaderSize ,MSG_WAITALL);
			}while(reciv==-1);

			if (getSizeFromHeader(paqueteReciv) > 0)
				reciv += recv(Descriptor_Cliente, paqueteReciv + HeaderSize, getSizeFromHeader(paqueteReciv), MSG_WAITALL);

			if (reciv>0){
				aSerializable.Data = paqueteReciv;
				Deserialize_Message(&aMsgRecv, aSerializable);
				Serializable_Free(&aSerializable);

				printf(" deseriallizo recive del sector : %i, tam %i \n",aMsgRecv.sectorRequest, reciv);
				if ((reciv>0)&&(aMsgRecv.sectorRequest >= 0)){
					socketReciveHD(&Planif, Descriptor_Cliente, aMsgRecv.sectorRequest, aMsgRecv.content, aMsgRecv.contentSize);
				}else{
					printf(" ERROR RN Recive \n");
					free(aMsgRecv.content);
				}
			}else{
			//if (reciv==0){
				printf (" caida de disco hd: %i, toque una tecla \n",Descriptor_Cliente);
				//scanf("asdasd");
				SocketHD_Disconect(&Planif, Descriptor_Cliente);
				printf (" FIN Proceso de Replanificacion caida de disco hd: %i \n",Descriptor_Cliente);

				free(paqueteReciv);
				pthread_exit(NULL);
				//break;
				//return  hiloRecive;
			}
		}
	}
	return  hiloRecive;
}

void* hiloConcect(void* param){
	int Descriptor;
	Descriptor = socket (AF_INET, SOCK_STREAM, 0);
	if (Descriptor == -1)
		printf ("Error\n");

	struct sockaddr_in Direccion;
	Direccion.sin_family = AF_INET;
	Direccion.sin_port = htons( atoi((const char*)archPPD->puertoPPD));
	Direccion.sin_addr.s_addr =INADDR_ANY;

	if (bind (Descriptor, (struct sockaddr *)&Direccion, sizeof (Direccion)) == -1)
	{
		close(Descriptor);
		printf ("Error bind\n");
	}

	if (listen (Descriptor, 1) == -1)
	{
		printf ("Error listen\n");
	}
	struct sockaddr Cliente;
	int Descriptor_Cliente;
	uint Longitud_Cliente;
	Longitud_Cliente = sizeof (Cliente);
	while (1){
		Descriptor_Cliente = accept (Descriptor, &Cliente, &Longitud_Cliente);
		if (Descriptor_Cliente == -1)
		{
			printf ("Error accept\n");
			return hiloConcect;
		}else{
			pthread_t hchd1;
			int phchd1 = pthread_create(&hchd1, NULL, hiloRecive, (void*)&Descriptor_Cliente);
			if (phchd1==0){
				//todobien
				printf("hilo Recive");
			}else{
				//hilo no creado.
				printf("error al crear hilo Recive ");
			}
		}
	}
	//close (Descriptor_Cliente);
	return hiloConcect;
}

int32_t RecivMensaje_FS(int32_t sock){
	char *paqueteReciv;
	Message aMsgReceived;
	Serializable_t aSerializable;
	int32_t reciv;
	paqueteReciv = (char*) malloc(600);
	do{
		reciv =0;
		reciv = recv(sock, paqueteReciv, HeaderSize, MSG_WAITALL);
		if (getSizeFromHeader(paqueteReciv) > 0)
			reciv += recv(sock, paqueteReciv + HeaderSize, getSizeFromHeader(paqueteReciv), MSG_WAITALL);
//		perror("recivMensaje");
	}while(reciv==-1);

	aSerializable.Data = paqueteReciv;
	Deserialize_Message(&aMsgReceived, aSerializable);
	Serializable_Free(&aSerializable);
	if (aMsgReceived.type == msgHandShake) { //Si el mensaje recibido es del tipo handshake solo hago el handshake
		send(sock, paqueteReciv, HeaderSize, 0);
		return 1;
	}

	if (aMsgReceived.type==1){ //lect
		SocketReciveFS_read(sock, aMsgReceived.sectorRequest);
	}else{
		SocketReciveFS_write(sock, aMsgReceived.sectorRequest, aMsgReceived.content, aMsgReceived.contentSize);
	}

	return reciv;
}
void* hiloConect_FS(void* param){
	int32_t socketEntrada = CreaConexionServer_INET(
					(char *) archPPD->puertoFS);
			struct sockaddr_in direccionRem;
			socklen_t direccionRemSize;
			int32_t sock1;
			fd_set fds;
			int32_t maxfd, i;

			FD_ZERO(&fds);
			FD_SET(socketEntrada, &fds);
			maxfd = socketEntrada;
			list_sockets aSocketList;
			nodo_socket * auxfd;
			aSocketList.list = NULL;

			while (1) {
				if (select(maxfd + 1, &fds, NULL, NULL, NULL) != -1) {
					for (i = 0; i <= maxfd; i++) {
						if (FD_ISSET(i, &fds)) { //Ocurrio un evento en el fd i
							if (list_sockets_exists(&aSocketList, i) != -1) { //Es un socket activo
								if (RecivMensaje_FS(i) == 0) { //Hay que cerrar este socket desp del send
									//si es 0 se cerro la conexion => cierro el fd
									close(i);
								}
							}
							else
							if (i == socketEntrada) {
								sock1 = accept(socketEntrada, (struct sockaddr*) &direccionRem, &direccionRemSize);
								list_sockets_add(&aSocketList, sock1);
								if(sock1 > maxfd)
									maxfd = sock1;
							}
						}
					}
					FD_ZERO(&fds);
					FD_SET(socketEntrada, &fds);
					auxfd = aSocketList.list;
					while(auxfd != NULL) {
						FD_SET(auxfd->descriptor, &fds);
						auxfd = auxfd->nxt;
					}

				}
			}

			close(socketEntrada);

			return hiloConect_FS;
}

int main(void){
	archPPD= configPPD();

	lista_init(&Pedidos);
	HDlista_init(&Planif);
	pthread_mutex_init(&s_pop_Pedido,NULL);
	pthread_mutex_init(&s_pop_HD[0],NULL);
	pthread_mutex_init(&s_pop_HD[1],NULL);
	pthread_mutex_init(&s_pop_HD[2],NULL);
	pthread_mutex_init(&s_pop_HD[3],NULL);

	printf ("PROCESO RAID EN ESPERA\n");

	pthread_t hcfs;
	int iretfs = pthread_create(&hcfs, NULL, hiloConect_FS, NULL);
	if (iretfs==0){
		//todobien
		//printf("hilo Conect creado");
	}else{
		//hilo no creado.
		printf("error al crear hilo ConectFS ");
	}

	pthread_t hcppd;
	int iretppd = pthread_create(&hcppd, NULL, hiloConcect, NULL);
	if (iretppd==0){
		//todobien
		//printf("hilo Conect creado");
	}else{
		//hilo no creado.
		printf("error al crear hilo Conect ");
	}

	//creo un hilo para el visor del FS
	pthread_t hFS;
	int iret1 = pthread_create(&hFS, NULL, ProcesarPedidosFS, (void*)(&Planif));
	if (iret1==0){
		//todobien
		//printf("hilo FS creado");
	}else{
		//hilo no creado.
		printf("error al crear hilo procesarPedidosFS");
	}

	//pruebas de escritura/lectura
	/*
	printf("precione tecla para simular pedidos de lectura del FS");
	scanf("asd");
	int i;

	for(i=0;i<=10;i++){
		SocketReciveFS_read(0, i);
	}

	for(i=10000;i<=10010;i++){
		void* buf=malloc(512);
		memset(buf, 0, 512);
		SocketReciveFS_write(0, i, buf, 512);
	}
	*/

	printf("FIN MAIN, ESPERANDO HILOS");

	pthread_join(hcfs,NULL);
	pthread_join(hcppd,NULL);
	pthread_join(hFS,NULL);

	return EXIT_SUCCESS;
}

